/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.core.runtime;

import org.proteusframework.core.api.*;
import org.proteusframework.core.api.config.IConfigurationLoader;
import org.proteusframework.core.api.ext.IAdapterManager;
import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.base.Namespace;
import org.proteusframework.core.base.NoSuchNamespaceException;
import org.proteusframework.core.util.Assert;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Provides a standard Proteus Framework application context suitable for nearly every imaginable application
 * built atop the Proteus Framework. Generally, an application will use code like this when bootstrapping
 * the application:
 * <p/>
 * <code>
 * IProteusApplication myApplication = new StandardProteusApplication();
 * </code>
 * <p/>
 * To access <b>Proteus Platform Services</b>, an application must include the platform services library in the
 * <code>CLASSPATH</code>, and use code like this when bootstrapping the application:
 * <p/>
 * <code>
 * IProteusApplication myApplication = new ExtendedProteusApplication();
 * </code>
 *
 * @author Tacoma Four
 */
public class StandardProteusApplication implements IProteusApplication
{
    /**
     * Logger.
     */
    private static final Logger logger = Logger.getLogger(StandardProteusApplication.class.getCanonicalName());

    /**
     * Runtime engine driving the application.
     */
    protected IRuntimeEngine engine;

    /**
     * Primordial factory registrar.
     */
    protected IFactoryRegistrar registrar;

    /**
     * Collection of lazily cached plugin registrars.
     */
    private Map<INamespace, IPluginRegistrar> pluginRegistrarCache = new HashMap<INamespace, IPluginRegistrar>();

    /**
     * {@inheritDoc}
     */
    @Override
    public void setRuntimeEngine(IRuntimeEngine engine)
    {
        Assert.parameterNotNull(engine, "Parameter 'engine' must not be null");

        this.engine = engine;

        if (logger.isLoggable(Level.FINEST))
        {
            logger.finest("Injecting platform delegate into runtime engine");
        }

        logger.config("Platform delegate is of type " + IPlatformDelegate.class.getName());
        this.engine.setPlatformDelegate(this, IPlatformDelegate.class);

        onRuntimeEngineInjected();

        if (logger.isLoggable(Level.FINEST))
        {
            logger.finest("Injected platform delegate into runtime engine");
        }

        if (logger.isLoggable(Level.INFO))
        {
            logger.info("A Standard Proteus Application is configured and ready to start the application");
        }
    }

    /**
     * Defines the runtime engine.
     *
     * @param engine Runtime engine
     */
    protected final void setEngine(IRuntimeEngine engine)
    {
        this.engine = engine;
    }

    /**
     * Access the runtime engine.
     *
     * @return Runtime engine
     */
    protected final IRuntimeEngine getEngine()
    {
        return engine;
    }

    /**
     * Hook for extensions to respond to the injection of the <code>IRuntimeEngine</code> instance into the app.
     */
    protected void onRuntimeEngineInjected()
    {

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final IAdapterManager getAdapterManager()
    {
        return AdapterManager.getInstance();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final <T extends IRuntimeEngine> T getEngine(Class<T> engineClassType)
    {
        T targetEngine = null;

        if (null == engine)
        {
            throw new IllegalStateException("The IRuntimeEngine isn't initialized and is unavailable at this time");
        }

        if (engineClassType.isAssignableFrom(engine.getClass()))
        {
            targetEngine = engineClassType.cast(engine);
        } else
        {
            throw new IllegalArgumentException("Application's IRuntimeEngine is not assignable to class " +
                    engineClassType.getName());
        }

        return targetEngine;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IPlatformDelegate> T adapt(Class<T> delegateClassType)
    {
        throw new IllegalArgumentException("Delegate cannot be adapted to " + delegateClassType.getName());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final synchronized IFactoryRegistrar getFactoryRegistrar()
    {
        if (null == registrar)
        {
            registrar = new ProteusRegistrar();
            registrar.setPlatformDelegate(this, IPlatformDelegate.class);
        }

        return registrar;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final IConfigurationLoader createConfigurationLoader()
    {
        return new StandardConfigurationLoader();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final synchronized IPluginRegistrar getPluginRegistrar(INamespace namespace)
            throws NoSuchNamespaceException
    {
        IPluginRegistrar pluginRegistrar;

        if (pluginRegistrarCache.containsKey(namespace))
        {
            pluginRegistrar = pluginRegistrarCache.get(namespace);
        } else
        {
            if (getFactoryRegistrar().isInstalled(namespace))
            {
                pluginRegistrar = getFactoryRegistrar().createRegistrar(namespace);
                if (null != pluginRegistrar)
                {
                    pluginRegistrarCache.put(namespace, pluginRegistrar);
                }
            } else
            {
                logger.severe("Unknown namespace: " + Namespace.toCanonical(namespace));
                throw new NoSuchNamespaceException(namespace);
            }
        }

        return pluginRegistrar;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void startApplication()
    {
        engine.run();
        onEngineStarted();
    }

    /**
     * Hook for extensions to respond to the application start event. The application engine's <code>run()</code> has
     * already been invoked when this event is fired.
     */
    protected void onEngineStarted()
    {

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void stopApplication()
    {
        engine.stop();
        onEngineStopped();
    }

    /**
     * Hook for extensions to respond to the application stop event. The application engine's <code>stop()</code> has
     * already been invoked when this event is fired.
     */
    protected void onEngineStopped()
    {

    }
}
